package com.orange.core.service.user;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.orange.core.mapper.credit.CreditFlowMapper;
import com.orange.core.mapper.shop.ShopMapper;
import com.orange.core.mapper.user.UserMapper;
import com.orange.core.pojo.PageReqDto;
import com.orange.core.pojo.entity.credit.CreditFlow;
import com.orange.core.pojo.entity.employee.EmployeeCard;
import com.orange.core.pojo.entity.shop.Shop;
import com.orange.core.pojo.entity.user.HistoryUserInfo;
import com.orange.core.pojo.entity.user.User;
import com.orange.core.pojo.entity.user.UserCoupon;
import com.orange.core.pojo.enums.EFlowBusinessType;
import com.orange.core.pojo.enums.EFlowType;
import com.orange.core.pojo.enums.EPayStatus;
import com.orange.core.pojo.enums.ERefundApplyStatus;
import com.orange.core.pojo.enums.ERefundStatus;
import com.orange.core.pojo.enums.EStatus;
import com.orange.core.pojo.enums.EUserType;
import com.orange.core.pojo.enums.EWriteOffStatus;
import com.orange.core.pojo.req.user.UserAvatarUpdateReq;
import com.orange.core.pojo.req.user.UserNicknameUpdateReq;
import com.orange.core.pojo.req.user.UserProfileUpdateReq;
import com.orange.core.pojo.req.user.UserSearchReq;
import com.orange.core.service.config.ConfigService;
import com.orange.core.service.employee.EmployeeCardService;
import com.orange.core.util.Constants;
import com.orange.core.util.PrincipalUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
@Service
public class UserService extends ServiceImpl<UserMapper, User> {

    private final EmployeeCardService employeeCardService;
    private final UserCouponService userCouponService;
    private final ConfigService configService;
    private final StringRedisTemplate redisTemplate;
    private final ShopMapper shopMapper;
    private final CreditFlowMapper creditFlowMapper;
    private final HistoryUserInfoService historyUserInfoService;
    private final ObjectMapper objectMapper;

    public Page<User> page(PageReqDto<UserSearchReq> req) {
        if (req.getCondition() == null) {
            req.setCondition(new UserSearchReq());
        }
        UserSearchReq condition = req.getCondition();
        Page<User> page = this.lambdaQuery()
                .eq(StrUtil.isNotBlank(condition.getUserNo()), User::getUserNo, condition.getUserNo())
                .eq(StrUtil.isNotBlank(condition.getPhone()), User::getPhone, condition.getPhone())
                .eq(condition.getStatus() != null, User::getStatus, condition.getStatus())
                .eq(condition.getType() != null, User::getType, condition.getType())
                .in(CollUtil.isNotEmpty(condition.getTypes()), User::getType, condition.getTypes())
                .eq(condition.getShopId() != null, User::getShopId, condition.getShopId())
                .like(StrUtil.isNotBlank(condition.getNickname()), User::getNickname, condition.getNickname())
                .page(req.toPage());
        List<User> users = page.getRecords();
        if (CollUtil.isNotEmpty(users)) {
            List<Integer> employeeIds = users.stream().filter(u -> EUserType.EMPLOYEE_TYPES.contains(u.getType())).map(User::getId).toList();
            if (CollUtil.isNotEmpty(employeeIds)) {
                Map<Integer, String> employeeCardMap = employeeCardService.lambdaQuery()
                        .in(EmployeeCard::getEmployeeId, employeeIds)
                        .list().stream().collect(Collectors.toMap(EmployeeCard::getEmployeeId, EmployeeCard::getCardNo));
                for (User user : users) {
                    user.setCardNo(employeeCardMap.get(user.getId()));
                }
            }
        }
        return page;
    }

    public User profile(Integer id) {
        User user = this.getById(id);
        Assert.notNull(user, "用户不存在");
        Assert.isTrue(Objects.equals(user.getStatus(), EStatus.NORMAL.getValue()), "用户已禁用，请联系管理员");

        Long couponCount = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, id)
                .eq(UserCoupon::getPayStatus, EPayStatus.PAID.getValue())
                .eq(UserCoupon::getWriteOffStatus, EWriteOffStatus.PENDING.getValue())
                .in(UserCoupon::getRefundApplyStatus, Arrays.asList(ERefundApplyStatus.NOT_APPLIED.getValue(),
                        ERefundApplyStatus.PENDING.getValue(),
                        ERefundApplyStatus.REJECTED.getValue(),
                        ERefundApplyStatus.CANCELED.getValue()))
                .eq(UserCoupon::getWriteOffStatus, EWriteOffStatus.PENDING.getValue())
                .eq(UserCoupon::getRefundStatus, ERefundStatus.NOT_REFUNDED.getValue())
                .eq(UserCoupon::getIsExpired, false)
                .count();
        user.setCouponCount(couponCount.intValue());

        if (EUserType.EMPLOYEE_TYPES.contains(user.getType())) {
            EmployeeCard employeeCard = employeeCardService.lambdaQuery()
                    .eq(EmployeeCard::getEmployeeId, user.getId())
                    .one();
            user.setCardNo(employeeCard.getCardNo());

            Shop shop = shopMapper.selectById(user.getShopId());
            user.setShopName(shop.getName());
        }

        return user;
    }

    @SneakyThrows
    public boolean updateProfile(UserProfileUpdateReq req) {
        Assert.isTrue(StrUtil.isNotBlank(req.getNickname()) || StrUtil.isNotBlank(req.getPhone()) || StrUtil.isNotBlank(req.getAvatar()), "请输入需更新的用户信息");
        this.lambdaUpdate()
                .set(StrUtil.isNotBlank(req.getNickname()), User::getNickname, req.getNickname())
                .set(StrUtil.isNotBlank(req.getPhone()), User::getPhone, req.getPhone())
                .set(StrUtil.isNotBlank(req.getAvatar()), User::getAvatar, req.getAvatar())
                .eq(User::getId, PrincipalUtil.getId())
                .update();

        if (StrUtil.isNotBlank(req.getPhone())) {
            HistoryUserInfo historyUserInfo = historyUserInfoService.lambdaQuery()
                    .eq(HistoryUserInfo::getPhone, req.getPhone())
                    .one();
            if (historyUserInfo == null || Objects.equals(historyUserInfo.getCredit(), 0)) {
                return true;
            }
            log.info("根据导入的用户信息更新现在用户积分：{}", objectMapper.writeValueAsString(historyUserInfo));
            User user = this.getById(PrincipalUtil.getId());
            this.lambdaUpdate()
                    .set(User::getCredit, user.getCredit() + historyUserInfo.getCredit())
                    .eq(User::getId, PrincipalUtil.getId())
                    .update();
        }
        return true;
    }

    @Transactional
    public int register(String openId, String unionId, String phone) {
        String userNo;
        User user;
        do {
            userNo = Constants.USER_PREFIX + RandomStringUtils.randomAlphanumeric(7).toUpperCase();
            user = this.lambdaQuery()
                    .eq(User::getUserNo, userNo)
                    .one();
        } while (user != null);

        int firstRegisterCredit = configService.getFirstRegisterCredit();

        user = new User();
        user.setUserNo(userNo);
        user.setNickname(RandomStringUtils.randomAlphanumeric(7));
        user.setPhone(phone);
        user.setStatus(EStatus.NORMAL.getValue());
        user.setType(EUserType.USER.getValue());
        user.setCredit(firstRegisterCredit);
        user.setWxOpenId(openId);
        user.setWxUnionId(unionId);
        this.save(user);

        // 保存积分流水
        creditFlowMapper.insert(new CreditFlow()
                .setUserId(user.getId())
                .setBusinessType(EFlowBusinessType.FIRST_REGISTER.getValue())
                .setBusinessId(user.getId())
                .setFlowType(EFlowType.OBTAIN.getValue())
                .setCredit(firstRegisterCredit)
                .setCreateBy(user.getId())
                .setCreateTime(LocalDateTime.now()));
        return user.getId();
    }

    @Transactional
    public boolean update(User user) {
        if (EUserType.EMPLOYEE_TYPES.contains(user.getType())) {
            Assert.isTrue(user.getShopId() != null, "请选择品牌商铺");
        }

        User dbUser = this.getById(user.getId());
        this.updateById(user);

        if (!Objects.equals(dbUser.getType(), user.getType())) {
            // 用户类型变了
            employeeCardService.updateEmployeeCard(user.getId(), user.getType(), user.getShopId(), dbUser);
        }

        return true;
    }

    public boolean setStatus(int id, EStatus status) {
        if (status != null) {
            // 清空token
            User user = this.getById(id);
            BoundSetOperations<String, String> ops = redisTemplate.boundSetOps(Constants.USER_TOKEN_REDIS_KEY + user.getPhone());
            String token;
            while (StrUtil.isNotBlank(token = ops.pop())) {
                redisTemplate.delete(Constants.USER_TOKEN_REDIS_KEY + token);
            }
            redisTemplate.delete(Constants.USER_TOKEN_REDIS_KEY + user.getPhone());

            return this.updateById(new User().setId(id).setStatus(status.getValue()));
        }
        return false;
    }

    public boolean switchMerchantAdmin(int id, int userType) {
        EUserType.getByValue(userType);
        this.lambdaUpdate()
                .set(User::getType, userType)
                .eq(User::getId, id)
                .update();
        return true;
    }

    public boolean updateNickname(UserNicknameUpdateReq req) {
        return this.lambdaUpdate()
                .set(User::getNickname, req.getNickname())
                .eq(User::getId, PrincipalUtil.getId())
                .update();
    }

    public boolean updateAvatar(UserAvatarUpdateReq req) {
        return this.lambdaUpdate()
                .set(User::getAvatar, req.getAvatar())
                .eq(User::getId, PrincipalUtil.getId())
                .update();
    }
}
